<!-- 
  @description: 虚拟化表格组件
  @功能点
    1. 虚拟化表格, 极大提升渲染速(以10000条简单基础数据一次性渲染为例, 普通表格耗时5.2s, 虚拟表格耗时0.001s)
    2. 支持数据加载状态的底部显示
    3. 支持自定义N行选中数据功能, 并提供框选高亮样式, 以及数据抛出
    3. 支持pageUp, pageDown切换行选中数据, 并进行自动滚动
-->
<script lang="jsx" setup>
const emit = defineEmits(["touchBottom"]);
const props = defineProps({
  tableData: { type: Array, default: [] },
  tableColumn: { type: Array, default: [] },
  tableLoading: { type: [String, null], default: null }, // more(加载更多), all(已无更多数据)
  config: { type: Object },
});
const config = computed(() => {
  return {
    // 表格头高度 支持: Number/Array<Number>
    headerHeight: 50,
    // 行高度
    rowHeight: 50,
    // 选中行相关功能参数
    tableActiveNum: 9, // 选中行数
    tableActiveKey: "id", // 选中行唯一Key
    pageUpDownEnabled: true, // 是否开启键盘pageUp, pageDown切换选中功能
    ...props.config,
  };
});

// 表格: 实例
const tableRef = ref(null);
// 表格: 页脚高度
const footerHeight = ref(0);
// 表格: 滚动相关
let scrollLeft = 0;
let scrollToTargetFlag = false;
let scrollToTargetTimer = null;

// 已选中: 数据
const tableActiveData = defineModel("tableActiveData", { default: () => [] });
// 已选中: 数据key组(计算属性缓存, 主要避免tableRowClass多次运算, 减少性能开销)
const tableActiveDataKeys = computed(() => tableActiveData.value.map((item) => item[config.value.tableActiveKey]));

// 切换选中行(点击表格行)
const tableRowClick = ({ rowData, rowIndex }) => {
  const { tableActiveNum, tableActiveKey } = config.value;
  // 屏蔽已选中数据行的重复点击
  if (tableActiveDataKeys.value.includes(rowData[tableActiveKey])) return;
  // 新选中的开始, 数据索引
  const new_startIndex = Math.floor(rowIndex / tableActiveNum) * tableActiveNum;
  const new_endIndex = new_startIndex + tableActiveNum;
  tableActiveData.value = props.tableData.slice(new_startIndex, new_endIndex);
};
// 切换选中行(pageUp, pageDown)
const handlePageUpDown = ({ code }) => {
  const { tableData } = props;
  const { tableActiveNum, tableActiveKey, pageUpDownEnabled } = config.value;
  if (!tableData.length || !pageUpDownEnabled) return;
  // 1. 基础参数(当前选中-开始, 结束索引)
  const now_startIndex = tableData.findIndex((rowData) => rowData[tableActiveKey] === tableActiveDataKeys.value.at(0));
  const now_endIndex = tableData.findIndex((rowData) => rowData[tableActiveKey] === tableActiveDataKeys.value.at(-1));
  // 2. 基础参数(新的选中-开始索引)
  let new_startIndex = null;
  let new_endIndex = null;
  // 3. 切换选中数据
  switch (code) {
    case "PageUp":
      if (now_startIndex === 0) return;
      new_startIndex = now_startIndex - tableActiveNum > 0 ? now_startIndex - tableActiveNum : 0;
      new_endIndex = new_startIndex + tableActiveNum - 1;
      tableActiveData.value = tableData.slice(new_startIndex, new_endIndex + 1);
      // 向上滚动 (以开始索引位置为准)
      scrollToTarget(new_startIndex, "PageUp");
      break;
    case "PageDown":
      if (now_endIndex === tableData.length - 1) return;
      new_startIndex = now_startIndex + tableActiveNum;
      new_endIndex = new_startIndex + tableActiveNum - 1;
      tableActiveData.value = tableData.slice(new_startIndex, new_endIndex + 1);
      // 向下滚动 (以结束索引位置为准)
      scrollToTarget(new_endIndex, "PageDown");
      break;
  }
};

// 表格滚动(滚轮)
const handleScroll = (params) => {
  if (!scrollToTargetFlag) {
    // 仅仅当Y轴进行偏移时, 才会进行如下操作
    if (scrollLeft === params.scrollLeft && params.yAxisScrollDir === "backward") {
      footerHeight.value = 0;
    }
    // 记录X轴偏移量
    scrollLeft = params.scrollLeft;
  }
};
// 表格滚动(指定) + 触底判断回调
const scrollToTarget = (rowIndex, code) => {
  if (rowIndex === null) return;
  clearTimeout(scrollToTargetTimer);
  scrollToTargetFlag = true;
  if (code === "PageUp") {
    footerHeight.value = 0;
  } else if (rowIndex >= props.tableData.length - 1) {
    footerHeight.value = 30;
    emit("touchBottom");
  }
  nextTick(() => {
    tableRef.value.scrollToRow(rowIndex);
    tableRef.value.scrollToLeft(scrollLeft);
    scrollToTargetTimer = setTimeout(() => {
      scrollToTargetFlag = false;
    }, 500);
  });
};

// 表格行: 样式
const tableRowClass = ({ rowData }) => {
  if (!tableActiveData.value.length) return;
  let className = "";
  const { tableActiveKey } = config.value;
  // 1.已选中数据: 第一条 => 增加上边框高亮样式
  if (rowData[tableActiveKey] === tableActiveDataKeys.value[0]) {
    className += " virtual-table-active-start";
  }
  // 2.已选中数据: 左右两边均增加高亮样式
  if (tableActiveDataKeys.value.includes(rowData[tableActiveKey])) {
    className += " virtual-table-active-row";
  }
  // 3.已选中数据: 最后一条 => 增加下边框高亮样式
  if (rowData[tableActiveKey] === tableActiveDataKeys.value[tableActiveDataKeys.value.length - 1]) {
    className += " virtual-table-active-end";
  }
  return className;
};

onMounted(() => {
  window.addEventListener("keydown", handlePageUpDown);
});
watch(
  () => props.tableLoading,
  (newValue) => {
    if (!newValue) footerHeight.value = 0;
  }
);
onUnmounted(() => {
  window.removeEventListener("keydown", handlePageUpDown);
});
</script>

<template>
  <el-auto-resizer>
    <template #default="{ height, width }">
      <el-table-v2
        ref="tableRef"
        id="virtualTable"
        fixed
        :data="tableData"
        :columns="tableColumn"
        :width="width"
        :height="height"
        :header-height="config.headerHeight"
        :row-height="config.rowHeight"
        :row-class="tableRowClass"
        :row-event-handlers="{ onClick: tableRowClick }"
        :footer-height="footerHeight"
        scrollbar-always-on
        @scroll="handleScroll"
        @end-reached="scrollToTarget(tableData.length - 1, 'PageDown')"
      >
        <!-- 空数据状态 -->
        <template #empty>
          <el-empty
            description="暂无数据"
            style="height: 100%"
          />
        </template>
        <!-- 底部状态 -->
        <template #footer>
          <div
            v-if="footerHeight"
            class="footer-box"
          >
            <el-divider v-if="tableLoading === 'more'">
              <el-button
                type="primary"
                text
                loading
                >正在加载更多数据</el-button
              >
            </el-divider>
            <el-divider v-else-if="tableLoading === 'all'"> 已无更多数据 </el-divider>
          </div>
        </template>
      </el-table-v2>
    </template>
  </el-auto-resizer>
</template>

<style lang="scss" scoped>
#virtualTable {
  // 表格行边框默认样式, 避免激活行样式切换发生抖动
  :deep(.el-table-v2__row) {
    border: 1px solid transparent;
  }
  // 激活行样式
  :deep(.el-table-v2__row.virtual-table-active-start) {
    border-top: 1px solid var(--el-color-primary);
  }
  :deep(.el-table-v2__row.virtual-table-active-row) {
    border-left: 1px solid var(--el-color-primary);
    border-right: 1px solid var(--el-color-primary);
  }
  :deep(.el-table-v2__row.virtual-table-active-end) {
    border-bottom: 1px solid var(--el-color-primary);
  }
  :deep(.el-table-v2__left) {
    .el-table-v2__row.virtual-table-active-row {
      border-right: none;
    }
  }
  // 表格空状态
  :deep(.el-table-v2__empty) {
    height: calc(100% - 50px);
  }
  // 页脚
  .footer-box {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    .el-divider {
      margin: 0;
    }
  }
}
</style>
